#include "ClientUapProxy.h"

#include <stdint.h>
#include <list>
#include <string>
#include "Trace.h"
#include "MediaLibrary.h"
#include "UAP.pb.h"
#include "UapEventConnectionReq.h"
#include "UapEventConnectionReleaseReq.h"
#include "UapEventMediaBearerSetupReq.h"
#include "UapEventGapMediaTransmissionFinishedInd.h"
#include "UapEventGapMediaBearerSetupCfm.h"
#include "UapEventGapMediaBearerSetupRej.h"
#include "UapGenerator.h"
#include "UapStateBase.h"

namespace tsc
{
namespace sgw
{

using namespace UserApplicationPart;
using ProtocolsCommon::PlayableItem;
using namespace tsc::common;

ClientUapProxy* ClientUapProxy::mInstance = 0;

/*******************************************************************************
 *
 * Constructor
 *
 ******************************************************************************/
ClientUapProxy::ClientUapProxy(
		RequestHandler* rh,
		GeneratorEnvironment& ge):
    mpReqHandler(rh),
    _ge(ge)
{
    INFO("Created ClientUapProxy");
} // ClientUapProxy::ClientUapProxy

/*******************************************************************************
 *
 * Destructor
 *
 ******************************************************************************/
ClientUapProxy::~ClientUapProxy()
{

}

//===================================================================
void ClientUapProxy::init(
		RequestHandler* rh,
		GeneratorEnvironment& ge)
{
    if(!mInstance)
    {
        mInstance = new ClientUapProxy(rh, ge);
    }
}

//===================================================================
ClientUapProxy&
ClientUapProxy::instance()
{
    if(!mInstance)
    {
        TRACE_ERROR("Trying to get instance of a proxy before initialization");
    }
    return *mInstance;
}

//===================================================================
void
ClientUapProxy::handlePdu(common::Message*& pMessage)
{
    if(!pMessage)
    {
        TRACE(1, "Received NULL Message - ignoring");
        return;
    }

    unsigned int socketConnId = pMessage->getConnectionId();

    UapPdu* uapPdu = new UapPdu();
    if(!uapPdu->ParseFromArray(pMessage->getData(), pMessage->getDataSize()))
    {
        TRACE(1, STR("Unable to parse received data to valid UapPdu"));
        DELETE_MESSAGE_P(pMessage);
        delete uapPdu;
        return;
    }
    if(!uapPdu->IsInitialized())
    {
        TRACE(1, STR("Received invalid UapPdu, errorStr: %s",
                     uapPdu->InitializationErrorString().c_str()));

        DELETE_MESSAGE_P(pMessage);
        delete uapPdu;
        return;
    }

    handleUapPdu(uapPdu, socketConnId);

    if(uapPdu)
    {
        delete uapPdu;
    }

    DELETE_MESSAGE_P(pMessage);
}

//===================================================================
void
ClientUapProxy::handleUapPdu(
    UserApplicationPart::UapPdu*& uapPdu,
    unsigned int socketConnectionId)
{
	common::EventData* event = 0;
	UapGenerator* generator = 0;
	uint32_t connectionId = uapPdu->connectionid();
	uint32_t clientId = connectionId >> 8;

	switch(uapPdu->pdunumber())
	{
		case UAP_PDU_CONNECTION_REQ:
		{
            UapConnectionRequest connReq;

            if (!connReq.ParseFromString(uapPdu->pdu()))
            {
                TRACE(1, STR("Received invalid UapConnectionReq message, errorStr: %s",
                             connReq.InitializationErrorString().c_str()));

                sendUapConnectionRej(connectionId,
                                     UserApplicationPart::CAUSE_SYNTAX_ERROR);

                break;
            }

			UserContext* pContext = 0;
			if(UserContext::findContext(clientId, pContext))
			{
				// context found but it shouldn't
				// do some cleanup of the context ??
			    TRACE(1, STR("UserContext for userClientId=0x%x(%u) was already created",
                             clientId, clientId));
			}
			else
			{
			    TRACE(3, STR("Creating UserContext for userClientId=0x%x(%u)",
			                 clientId, clientId));
			    pContext = new UserContext(clientId);
			}
			TRACE(3, STR("Created UapGenerator for clientId=0x%x, connId=0x%x",
			             clientId, connectionId));

			generator = new UapGenerator(_ge, pContext, connectionId);

			event = new UapEventConnectionReq(connReq, socketConnectionId);
		}
		break;

		case UAP_PDU_CONNECTION_RELEASE_REQ:
		{
			UapConnectionReleaseReq connRelReq;
			if(!connRelReq.ParseFromString(uapPdu->pdu()))
			{
			    TRACE(1, STR("Received invalid UapConnectionReleaseReq message, errorStr: %s",
			                 connRelReq.InitializationErrorString().c_str()));

				break;
			}

			TRACE(4, STR("Received UapConnRelReq, connId=0x%x", connectionId));

            UserContext* context = 0;
            if(!UserContext::findContext(clientId, context))
            {
                // context not found
                TRACE(1, STR("UserContext for clientId=0x%x not found - discarding",
                             clientId));

                break;
            }

			if(!UapGenerator::findGenerator(connectionId, generator))
			{
			    TRACE(1, STR("UapGenerator for connId=0x%x not found - discarding",
			                 connectionId));
				break;
			}

			event = new UapEventConnectionReleaseReq(connRelReq);
		}
		break;

		case UAP_PDU_MEDIA_BEARER_SETUP_REQ:
		{
		    UapMediaBearerSetupRequest bearerSetupReq;

            if (!bearerSetupReq.ParseFromString(uapPdu->pdu()))
            {
                TRACE(1, STR("Received invalid UapMediaBearerSetupRequest message, errorStr: %s",
                             bearerSetupReq.InitializationErrorString().c_str()));

                break;
            }

            TRACE(4, STR("Received UapMediaBearerSetupReq, connId=0x%x", connectionId));

			UserContext* context = 0;
			if(!UserContext::findContext(clientId, context))
			{
			    TRACE(1, STR("UserContext for clientId=0x%x not found - send reject",
                             clientId));

				if(!sendUapMediaBearerSetupRej(
				        connectionId,
				        UserApplicationPart::CAUSE_UNSPECIFIED))
				{
				    TRACE(1, STR("Failed to send UapMediaBearerSetupRej message, "
				                 "connId=0x%x",
				                 connectionId));
				}
				break;
			}

			if(!UapGenerator::findGenerator(connectionId, generator))
            {
			    TRACE(1, STR("UapGenerator for connId=0x%x not found - send reject",
                             connectionId));

                if(!sendUapMediaBearerSetupRej(
                        connectionId,
                        UserApplicationPart::CAUSE_UNSPECIFIED))
                {
                    TRACE(1, STR("Failed to send UapMediaBearerSetupRej message, "
                                 "connId=0x%x",
                                 connectionId));
                }
                break;
            }

			event = new UapEventMediaBearerSetupReq(bearerSetupReq);
		}
		break;

		case UAP_PDU_TIMER:
		{
		    ProtocolsCommon::TimerExpired timerExp;
		    if (!timerExp.ParseFromString(uapPdu->pdu()))
            {
                TRACE(1, STR("Received invalid TimerExpired message, errorStr: %s",
                             timerExp.InitializationErrorString().data()));

                break;
            }

            TRACE(4, STR("Received TimerExpired, connId=0x%x", connectionId));

            UserContext* context = 0;
            if(!UserContext::findContext(clientId, context))
            {
                TRACE(1, STR("UserContext for clientId=0x%x not found - ignoring event",
                             clientId));
                break;
            }

            if(!UapGenerator::findGenerator(connectionId, generator))
            {
                TRACE(1, STR("UapGenerator for connId=0x%x not found - ignoring event",
                             connectionId));
                break;
            }

            if(timerExp.timerid() == UAP_TIMER_ID_T643)
            {
                event = new UapEventT643Expired();
            }
            else
            {
                TRACE(1, STR("Unexpected timerId=%u", timerExp.timerid()));
            }
		}
		break;

		default:
			TRACE(1, STR("Received unknown UapPdu with pduNum=0x%x(%u)",
			             uapPdu->pdunumber(), uapPdu->pdunumber()));
	}

	if(event)
	{
		generator->injectEvent(event);

		// check if event was handled
		if(event)
		{
		    TRACE_ERROR(STR("Event:sigNo=0x%x not handled", event->sigNo()));
			delete event;
		}
	}
	if(uapPdu)
	{
		delete uapPdu;
		uapPdu = 0;
	}
}

//===================================================================
void
ClientUapProxy::handleGapMediaTransmissionFinishedInd(
    uint32_t connectionId)
{
    UapGenerator* gen = 0;
    if(!UapGenerator::findGenerator(connectionId, gen))
    {
        // TODO logging
        std::cout << "There is no UapGenerator for connectionId=0x"
                << std::hex << connectionId << std::endl;
        return;
    }

    common::EventData* event =
            new UapEventGapMediaTransmissionFinishedInd(connectionId);
    gen->injectEvent(event);

    if(event)
    {
        std::cout << "Error, event not handled, sigNo=0x" << std::hex <<
                event->sigNo() << std::endl;
        delete event;
    }
}

//===================================================================
void
ClientUapProxy::handleGapMediaBearerSetupCfm(
    uint32_t connectionId,
    uint32_t serverMediaPort,
    const ProtocolsCommon::IpAddress* serverMediaAddress)
{
    UapGenerator* gen = 0;
    if(!UapGenerator::findGenerator(connectionId, gen))
    {
        // TODO logging
        std::cout << "There is no UapGenerator for connectionId=0x"
                << std::hex << connectionId << std::endl;
        return;
    }

    common::EventData* event =
            new UapEventGapMediaBearerSetupCfm(serverMediaPort, serverMediaAddress);
    gen->injectEvent(event);

    if(event)
    {
        std::cout << "Error, event not handled, sigNo=0x" << std::hex
                  << event->sigNo() << std::endl;
        delete event;
    }
}

//===================================================================
void
ClientUapProxy::handleGapMediaBearerSetupRej(
    uint32_t connectionId,
    GatewayApplicationPart::Cause cause)
{
    UapGenerator* gen = 0;
    if(!UapGenerator::findGenerator(connectionId, gen))
    {
        // TODO logging
        std::cout << "There is no UapGenerator for connectionId=0x"
                << std::hex << connectionId << std::endl;
        return;
    }

    common::EventData* event =
                new UapEventGapMediaBearerSetupRej(cause);
    gen->injectEvent(event);

    if(event)
    {
        std::cout << "Error, event not handled, sigNo=0x" << std::hex
                  << event->sigNo() << std::endl;
        delete event;
    }
}


//===================================================================
bool
ClientUapProxy::sendUapConnectionCfm(UapGenerator& uapGen)
{
    UapConnectionConfirm connCfm;

    std::list<PlayableItem*>* availableMedia =
            MediaLibrary::instance().getAvailableMedia(uapGen.getCapabilities());

    for(std::list<PlayableItem*>::iterator it = availableMedia->begin();
            it != availableMedia->end();
            ++it)
    {
        PlayableItem* item = connCfm.add_items();
        item->CopyFrom(**it);
    }

    // XXX what about message validation?

    if(!ClientUapProxy::instance().sendUapPdu(
            uapGen.getConnectionId(),
            connCfm.SerializeAsString(),
            UAP_PDU_CONNECTION_CFM))
    {
        // TODO print error
        return false;
    }
    return true;
}

//===================================================================
bool
ClientUapProxy::sendUapConnectionRej(
		uint32_t                   connectionId,
		UserApplicationPart::Cause cause)
{
	UapConnectionReject connRej;
	connRej.set_cause(cause);

	// XXX what about message validation?

	if(!sendUapPdu(connectionId,
				   connRej.SerializeAsString(),
				   UAP_PDU_CONNECTION_REJ))
	{
		// TODO print error
		return false;
	}
	return true;
}

//===================================================================
bool
ClientUapProxy::sendUapMediaBearerSetupCfm(UapGenerator& uapGenerator)
{
    UapMediaBearerSetupConfirm bearerSetupCfm;

    bearerSetupCfm.set_servermediaport(uapGenerator.getServerMediaPort());
    bearerSetupCfm.set_allocated_servermediaaddress(uapGenerator.getServerMediaAddress());

    // XXX what about message validation?

    if(!sendUapPdu(uapGenerator.getConnectionId(),
                   bearerSetupCfm.SerializeAsString(),
                   UAP_PDU_MEDIA_BEARER_SETUP_CFM))
    {
        // TODO print error
        return false;
    }
    return true;
}

//===================================================================
bool
ClientUapProxy::sendUapMediaBearerSetupRej(
        uint32_t                   connectionId,
        UserApplicationPart::Cause cause)
{
    UapMediaBearerSetupReject bearerSetupRej;
    bearerSetupRej.set_cause(cause);

    if(!sendUapPdu(connectionId,
                   bearerSetupRej.SerializeAsString(),
                   UAP_PDU_MEDIA_BEARER_SETUP_REJ))
    {
        // TODO print error
        return false;
    }
    return true;
}

//===================================================================
bool
ClientUapProxy::sendUapPdu(
		uint32_t           connectionId,
		const std::string& msg,
		UapPduNumber       pduNum)
{
	UapPdu uapPdu;
	uapPdu.set_connectionid(connectionId);
	uapPdu.set_pdu(msg);
	uapPdu.set_pdunumber(pduNum);

	if(!uapPdu.IsInitialized())
	{
		TRACE(1, STR("Unable to encode UapPdu, errorStr=%s",
		             uapPdu.InitializationErrorString().data()));
		return false;
	}

	unsigned int socketConnId = 0;
	if(!getSocketId(connectionId, socketConnId))
	{
	    TRACE(1, STR("Cannot find socketId for connectionId=0x%x(%u)",
	                 connectionId, connectionId));
	    return false;
	}

	std::string encodedUapPdu;
	uapPdu.SerializeToString(&encodedUapPdu);

	Message* pMessage = new Message(
	        encodedUapPdu.data(),
	        encodedUapPdu.size(),
	        socketConnId);

	// send the message
	if(mpReqHandler->putRequestOnOutQueue(pMessage) == REQUEST_IS_ON_QUEUE)
	{
	    // request handler claims ownership of the message
	    pMessage = 0;
	    return true;
	}
	return false;
}

//===================================================================
void
ClientUapProxy::addConnIdMapping(
    uint32_t generatorId,
    unsigned int socketConnId)
{
    mSocketIdtoGenIdMap.insert(
            std::pair<uint32_t, unsigned int>(generatorId, socketConnId));
}

//===================================================================
void
ClientUapProxy::clearConnIdMapping(uint32_t generatorId)
{
    mSocketIdtoGenIdMap.erase(generatorId);
}

//===================================================================
bool
ClientUapProxy::getSocketId(
    uint32_t generatorId,
    unsigned int& socketId)
{
    std::map<uint32_t, unsigned int>::iterator it;
    it = mSocketIdtoGenIdMap.find(generatorId);
    if(it != mSocketIdtoGenIdMap.end())
    {
        socketId = it->second;
        return true;
    }
    return false;
}

common::RequestHandler*
ClientUapProxy::getRequestHandler()
{
    return mpReqHandler;
}

} // namespace sgw
} // namespace tsc
